package main

import (
	"fmt"
	"sort"

	"github.com/eiannone/keyboard"
)

func main() {
	//onBasicTypes()
	//onAggregateTypes()
	//onReferenceTypes()
	//onMap()
	//onFunction()
	//onChannels()
	//onInterface()
	//onExpressions()
	//onBooleans()
	onComposition()
}

//
// 组合
//

type VehicleX struct {
	NumberOfWheels     int
	NumberOfPassengers int
}

type CarX struct {
	Make       string
	Model      string
	Year       int
	IsElectric bool
	IsHybird   bool
	Vehicle    VehicleX
}

func (v VehicleX) showDetails() {
	fmt.Println("Number of passengers", v.NumberOfPassengers)
	fmt.Println("Number of wheels", v.NumberOfWheels)
}

func (c CarX) show() {
	fmt.Println("Make:", c.Make)
	fmt.Println("Model:", c.Model)
	fmt.Println("Year:", c.Year)
	fmt.Println("IsElectric:", c.IsElectric)
	fmt.Println("IsHybird:", c.IsHybird)
	c.Vehicle.showDetails()
}

func onComposition() {
	suv := VehicleX{
		NumberOfWheels:     4,
		NumberOfPassengers: 6,
	}

	volvoXC90 := CarX{
		Make:       "Volvo",
		Model:      "XC90 T8",
		Year:       2021,
		IsElectric: false,
		IsHybird:   true,
		Vehicle:    suv,
	}

	// 我开始以为Composition有多么奇怪。这不就是一个结构里面有另外一个结构吗？
	volvoXC90.show()
}

// 布尔和组合（Compound）布尔
type Employee struct {
	Name     string
	Age      int
	Salary   int
	FullTime bool
}

func onBooleans() {
	apples := 18
	oranges := 9

	// 布尔运算符：> < >= <=
	fmt.Println(apples == oranges)
	fmt.Println(apples != oranges)

	jack := Employee{
		Name:     "Jack Smith",
		Age:      27,
		Salary:   40000,
		FullTime: false,
	}

	jill := Employee{
		Name:     "Jill Jones",
		Age:      33,
		Salary:   60000,
		FullTime: true,
	}

	var empolyees []Employee
	empolyees = append(empolyees, jack)
	empolyees = append(empolyees, jill)

	for _, x := range empolyees {
		if x.Age > 30 {
			fmt.Println(x.Name, "is 30 or older")
		} else {
			fmt.Println(x.Name, "is under 30")
		}

		if x.Age > 30 && x.Salary > 50000 {
			fmt.Println(x.Name, "makes more than 50000 and is over 30")
		} else {
			fmt.Println("Either", x.Name, "makes less than 50000 or is under 30")
		}

		// if默认情况下是不需要用括号的。
		if x.Age > 30 || x.Salary > 50000 {
			fmt.Println(x.Name, "makes more than 50000 or is over 30")
		} else {
			fmt.Println(x.Name, "makes less than 50000 and is under 30")
		}

		// 使用括号来使判断更加明确
		if (x.Age > 30 || x.Salary < 50000) && x.FullTime {
			fmt.Println(x.Name, "matches our unclear criteria")
		}
	}
}

// 表达式
func onExpressions() {
	age := 10
	name := "Jack"
	rightHanded := true

	fmt.Printf("%s is %d years old. Right handed:%t\n", name, age, rightHanded)

	// age + 10这是一个表达式
	// age >= 13也是一个表达式
	// 所以，只要能计算出一个单一的值的东西，就是一个表达式。
	ageInTenYears := age + 10
	fmt.Println("In ten years, %s will be %d years old", name, ageInTenYears)

	isATeenager := age >= 13
	fmt.Println(name, "is a teenager:", isATeenager)

	// 出错：表达式只能做为右值
	// age + 10 := 20
}

//
// 接口类型
//

// 接口只定义方法
type AnimalX interface {
	Says() string
	HowManyLegs() int
}

type DogX struct {
	Name         string
	Sound        string
	NumberOfLegs int
}

type CatX struct {
	Name         string
	Sound        string
	NumberOfLegs int
	HasTail      bool
}

// 通过Receiver来定义一个接口
func (d *DogX) Says() string {
	return d.Sound
}
func (d *DogX) HowManyLegs() int {
	return d.NumberOfLegs
}
func (d *CatX) Says() string {
	return d.Sound
}

func (d *CatX) HowManyLegs() int {
	return d.NumberOfLegs
}

func onInterface() {
	dog := DogX{
		Name:         "dog",
		Sound:        "woof",
		NumberOfLegs: 4,
	}

	// 这里调用的语法，是用寻址符号。
	// 但是定义函数时AnimalX不需要用指针，所以接口天生就是引用类型。
	Riddle(&dog)

	var cat CatX
	cat.Name = "cat"
	cat.NumberOfLegs = 4
	cat.Sound = "meow"
	cat.HasTail = true

	Riddle(&cat)
}

func Riddle(d AnimalX) {
	riddle := fmt.Sprintf(`This animal says "%s" and has %d legs. What animal is it?`, d.Says(), d.HowManyLegs())
	fmt.Println(riddle)
}

// 引用类型(pointers, slices, maps, functions, channels)
func onReferenceTypes() {
	var x int
	x = 10

	myFirstPointer := &x

	fmt.Println("x is", x)
	fmt.Println("pointer is", myFirstPointer)

	*myFirstPointer = 15
	fmt.Println("x is now", x)

	changeValueOfPointer(&x)
	fmt.Println("x is now", x)

	// 这就是slice，不指定固定的数量，就是slice？
	var animals []string
	animals = append(animals, "dog")
	animals = append(animals, "fish")
	animals = append(animals, "cat")
	animals = append(animals, "horse")
	fmt.Println(animals)

	for i, x := range animals {
		fmt.Println(i, x)
	}

	fmt.Println("Element 0 is", animals[0])
	fmt.Println("First two elements are", animals[0:2])
	fmt.Println("The slice is", len(animals), "elements long")

	fmt.Println("Is it sorted?", sort.StringsAreSorted(animals))
	sort.Strings((animals))
	fmt.Println("Is it sorted now?", sort.StringsAreSorted(animals))
	fmt.Println(animals)

	// 从这里看到。slice是传引用的。在函数中对slice进行的修改反应到了animals中。
	animals2 := deleteFromSlice(animals, 1)
	fmt.Println("animals After delete index = 1", len(animals), animals)
	fmt.Println("animals2 After delete index = 1", len(animals2), animals2)
}

func changeValueOfPointer(num *int) {
	*num = 25
}

func deleteFromSlice(a []string, i int) []string {
	// 删除一个项目，这里非常蛋痛的做法。因为slice是一个基础结构，所以才会这么蛋痛？

	// 第1步，将最后一个项目赋值给索引指定的项目
	a[i] = a[len(a)-1]
	// 第2步，将最后一个项目指定为空（教程中使用了这一步，但实际上没有任何意义）
	a[len(a)-1] = ""
	// 剪裁数组，剪裁掉最后一个项目
	a = a[:len(a)-1]

	// 这里蛋痛的就是slice不再是被排序好的了。
	// 除非你每个都向前移动。
	return a
}

func onMap() {
	// 在go语言中使用map的奇葩地方。他不能直接定义，而是使用make函数来创建
	intMap := make(map[string]int)

	intMap["one"] = 1
	intMap["two"] = 2
	intMap["three"] = 3
	intMap["four"] = 4
	intMap["five"] = 5

	// 这里打印的项目并不是按顺序排列。每次遍历的时候顺序都会不同。
	// PS（但是我是每次重新使用go run，如果在同一个程序下呢？）
	for key, value := range intMap {
		fmt.Println(key, value)
	}

	delete(intMap, "four")
	fmt.Println("After delete:")
	for key, value := range intMap {
		fmt.Println(key, value)
	}

	// 这样获得一个项目
	el, ok := intMap["four"]
	if ok {
		fmt.Println(el, "is in map")
	} else {
		fmt.Println(el, "is not in map")
	}

	intMap["two"] = 4
	fmt.Println("After change:")
	for key, value := range intMap {
		fmt.Println(key, value)
	}

	// TIPS：Map中寻址非常快速。所以在go语言中非常常用。要多用。
}

type Animal struct {
	Name         string
	Sound        string
	NumberOfLegs int
}

// 这东西叫接受器（Receiver）
// 语法就是这样，在func后面跟一个指针。然后就可以用Animal的对象调用了。
// 如此奇葩。因为GO不是面向对象的语言。他使用一个这样奇葩的构架。
func (a *Animal) Says() {
	fmt.Printf("A %s says %s\n", a.Name, a.Sound)
}

func (a *Animal) HowManyLegs() {
	fmt.Printf("A %s has %d legs\n", a.Name, a.NumberOfLegs)
}

func onFunction() {
	z := addTwoNumber(2, 4)
	fmt.Println("2 + 4 =", z)

	myTotal := sumMany(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
	fmt.Println("1-10 =", myTotal)

	var dog Animal
	dog.Name = "dog"
	dog.Sound = "woof"
	dog.NumberOfLegs = 4
	dog.Says()

	cat := Animal{
		Name:         "cat",
		Sound:        "meow",
		NumberOfLegs: 4,
	}
	cat.Says()

	cat.HowManyLegs()
}

func addTwoNumber(x, y int) int {
	return x + y
}

// 使用可变参数
func sumMany(nums ...int) int {
	total := 0
	for _, x := range nums {
		total += x
	}
	return total
}

// 这里表示这个channel接受一个rune（字符）
// 这里仅仅是一个声明，这个keyPressChan只是一个空项目
var keyPressChan chan rune

func onChannels() {
	// channel同map一样，也只能用make来创建
	keyPressChan = make(chan rune)
	go listenForKeyPress()
	fmt.Println("Press any key, or q to quit")
	_ = keyboard.Open()
	defer func() {
		keyboard.Close()
	}()

	for {
		char, _, _ := keyboard.GetSingleKey()
		if char == 'q' || char == 'Q' {
			break
		}
		// 这里非常奇葩的语法，使用一个<-
		keyPressChan <- char
	}
}

func listenForKeyPress() {
	for {
		// 这个语法接受一个channel信息
		// 这里这个语法就使这个routine被阻塞！
		// 这东西的确太方便了，难怪服务器要用这个。
		key := <-keyPressChan
		fmt.Println("You pressed", string(key))
	}
}

func onChannels_Discard() {
	// 这个语法开启一个routine
	go doSomething("Hello, world")

	fmt.Println("This is another message")

	for {
		// infinity
	}
}

func doSomething(s string) {
	until := 0
	for {
		fmt.Println("s is", s)
		until = until + 1
		if until >= 5 {
			break
		}
	}
}

// 聚合/集合类型(array, struct)

type Car struct {
	NumberOfTires int
	Luxury        bool
	BucketSeats   bool
	Make          string
	Model         string
	Year          int
}

func onAggregateTypes() {
	// 教程中提到了一个东西，在实际编程中，很少使用array，多用的是slice.
	var myStrings [3]string

	myStrings[0] = "cat"
	myStrings[1] = "dog"
	myStrings[2] = "fish"

	fmt.Println("First element in array is", myStrings[0])

	var myCar Car
	myCar.NumberOfTires = 4
	myCar.Luxury = false
	myCar.Make = "Volkswagen"

	// 另外一种定义的方法，直接设置
	myCar2 := Car{
		NumberOfTires: 4,
		Luxury:        true,
		BucketSeats:   true,
		Make:          "Volvo",
		Model:         "XC()",
		Year:          2019,
	}

	fmt.Printf("My car is a %d %s %s\n", myCar.Year, myCar.Make, myCar.Model)
	fmt.Printf("My car2 is a %d %s %s\n", myCar2.Year, myCar2.Make, myCar2.Model)
}

// 基础类型(numbers, strings, booleans)
var myInt int
var myInt16 int16
var myInt32 int32
var myInt64 int64
var myUint uint
var myFloat float32
var myFloat64 float64

func onBasicTypes() {
	myInt = 10
	myUint = 20 // 这里如果设置负值，会报错。c++是不会的

	myFloat = 10.1
	myFloat64 = 100.1

	fmt.Println(myInt, myUint, myFloat, myFloat64)

	myString := "Ada"
	fmt.Println(myString)

	// GO中的字符串是不能改变的。这个创建一个新的字符串项目，改变其应用。
	myString = "John"

	var myBool = true
	myBool = false // 在GO中，是不能将其它的值赋值给BOOL的。和c++不同。
	fmt.Println(myBool)
}
